<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>28 Scheduler 基本观念 · ShaofeiZi Blog · 做个日常记录</title>
    <meta name="description" content="訾绍飞的博客。万物皆有裂缝处，那是光射进来的地方。">
    <link rel="shortcut icon" href="/BLOG/favicon.ico">
  <link rel="manifest" href="/BLOG/manifest.json">
  <meta name="theme-color" content="#3F51B5">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <link rel="apple-touch-icon" href="/BLOG/icons/192.png">
  <link rel="mask-icon" href="/BLOG/icons/safari-pinned-tab.svg" color="#3eaf7c">
  <meta name="msapplication-TileImage" content="/icons/192.png">
  <meta name="msapplication-TileColor" content="#3F51B5">
    
    <link rel="preload" href="/BLOG/assets/css/42.styles.90045bd1.css" as="style"><link rel="preload" href="/BLOG/assets/js/app.1a725be8.js" as="script"><link rel="preload" href="/BLOG/assets/js/6.e51cd79c.js" as="script"><link rel="prefetch" href="/BLOG/assets/js/7.88ba0bb7.js"><link rel="prefetch" href="/BLOG/assets/js/0.d3e592bd.js"><link rel="prefetch" href="/BLOG/assets/js/1.39b9c99c.js"><link rel="prefetch" href="/BLOG/assets/js/2.68dc10c9.js"><link rel="prefetch" href="/BLOG/assets/js/3.dfebdd5e.js"><link rel="prefetch" href="/BLOG/assets/js/4.ea97a821.js"><link rel="prefetch" href="/BLOG/assets/js/5.d8c2ecbf.js"><link rel="prefetch" href="/BLOG/assets/js/8.d9eebc06.js"><link rel="prefetch" href="/BLOG/assets/js/9.1a541d13.js"><link rel="prefetch" href="/BLOG/assets/js/10.4ec9ca67.js"><link rel="prefetch" href="/BLOG/assets/js/11.02558377.js"><link rel="prefetch" href="/BLOG/assets/js/12.d0e2086f.js"><link rel="prefetch" href="/BLOG/assets/js/13.5af02ddd.js"><link rel="prefetch" href="/BLOG/assets/js/14.5d9fcbf2.js"><link rel="prefetch" href="/BLOG/assets/js/15.ca0178b2.js"><link rel="prefetch" href="/BLOG/assets/js/16.cd99d056.js"><link rel="prefetch" href="/BLOG/assets/js/17.56f11c1d.js"><link rel="prefetch" href="/BLOG/assets/js/18.21837cc7.js"><link rel="prefetch" href="/BLOG/assets/js/19.73335fea.js"><link rel="prefetch" href="/BLOG/assets/js/20.1632ab79.js"><link rel="prefetch" href="/BLOG/assets/js/21.43175244.js"><link rel="prefetch" href="/BLOG/assets/js/22.5b7c0cca.js"><link rel="prefetch" href="/BLOG/assets/js/23.e624ba97.js"><link rel="prefetch" href="/BLOG/assets/js/24.ac5f7b41.js"><link rel="prefetch" href="/BLOG/assets/js/25.6934a11d.js"><link rel="prefetch" href="/BLOG/assets/js/26.407b2583.js"><link rel="prefetch" href="/BLOG/assets/js/27.7449d673.js"><link rel="prefetch" href="/BLOG/assets/js/28.52e25437.js"><link rel="prefetch" href="/BLOG/assets/js/29.fba21f3a.js"><link rel="prefetch" href="/BLOG/assets/js/30.2cd6d3e2.js"><link rel="prefetch" href="/BLOG/assets/js/31.0b0a749f.js"><link rel="prefetch" href="/BLOG/assets/js/32.92134487.js"><link rel="prefetch" href="/BLOG/assets/js/33.ad2b89cc.js"><link rel="prefetch" href="/BLOG/assets/js/34.9b22334e.js"><link rel="prefetch" href="/BLOG/assets/js/35.825f3d75.js"><link rel="prefetch" href="/BLOG/assets/js/36.cc3da84c.js"><link rel="prefetch" href="/BLOG/assets/js/37.8f339f62.js"><link rel="prefetch" href="/BLOG/assets/js/38.5674618f.js"><link rel="prefetch" href="/BLOG/assets/js/39.180f0d85.js"><link rel="prefetch" href="/BLOG/assets/js/40.275f26e3.js"><link rel="prefetch" href="/BLOG/assets/js/41.ce0f5927.js">
    <link rel="stylesheet" href="/BLOG/assets/css/42.styles.90045bd1.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div data-app="true" id="app" class="application theme--light"><div class="application--wrap"><div class="v-progress-linear blog-progress" style="height:3px;display:none;"><div class="v-progress-linear__background accent" style="height:3px;opacity:0.4;width:100%;"></div><div class="v-progress-linear__bar"><!----><div class="v-progress-linear__bar__determinate accent" style="width:0%;"></div></div></div><aside class="v-navigation-drawer v-navigation-drawer--close v-navigation-drawer--fixed v-navigation-drawer--is-mobile" style="height:100%;margin-top:0px;max-height:calc(100% - 0px);transform:translateX(-240px);width:240px;"><div><div class="aside-brand-wrap"><div class="aside-brand"><a href="/BLOG/" class="aside-avatar elevation-2 router-link-active"><img src="/BLOG/face.png" alt="avatar"></a><hgroup class="mt-3 variant-hide"><div class="subheading white--text">訾绍飞</div><a href="mailto:zishaofei221@gmail.com" title="zishaofei221@gmail.com" class="aside-mail primary--text text--lighten-5">zishaofei221@gmail.com</a></hgroup></div></div><hr class="v-divider theme--dark"><div class="v-list nav-list"><div class="secondary--text"><a href="/BLOG/" class="v-list__tile v-list__tile--link"><div class="v-list__tile__avatar"><div class="v-avatar" style="height:40px;width:40px;"><i class="fa fa-home"></i></div></div><div class="v-list__tile__content">首页</div></a></div><div class="secondary--text"><a href="/BLOG/tags" class="v-list__tile v-list__tile--link"><div class="v-list__tile__avatar"><div class="v-avatar" style="height:40px;width:40px;"><i class="fa fa-tag"></i></div></div><div class="v-list__tile__content">标签</div></a></div><div class="secondary--text"><a href="https://github.com/ShaofeiZi" target="_blank" class="v-list__tile v-list__tile--link"><div class="v-list__tile__avatar"><div class="v-avatar" style="height:40px;width:40px;"><i class="fab fa-github"></i></div></div><div class="v-list__tile__content">Github</div></a></div><div class="secondary--text"><a href="/BLOG/about" class="v-list__tile v-list__tile--link"><div class="v-list__tile__avatar"><div class="v-avatar" style="height:40px;width:40px;"><i class="fa fa-user-secret"></i></div></div><div class="v-list__tile__content">About</div></a></div></div></div><div class="v-navigation-drawer__border"></div></aside><nav class="blog-toolbar v-toolbar v-toolbar--fixed theme--dark primary" style="margin-top:0px;padding-right:0px;padding-left:0px;transform:translateY(0px);"><div class="v-toolbar__content" style="height:56px;"><button type="button" class="v-btn v-btn--icon"><div class="v-btn__content"><i class="fa fa-bars"></i></div></button><div class="v-toolbar__title">28 Scheduler 基本观念</div><div class="spacer"></div><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""><!----></div><div class="v-menu" style="display:none;"><div class="v-menu__activator"><button type="button" class="v-btn v-btn--icon"><div class="v-btn__content"><i class="fa fa-share-alt"></i></div></button></div><div class="v-menu__content" style="max-height:auto;min-width:0px;max-width:auto;top:12px;left:0px;transform-origin:top right;z-index:0;display:none;"><div class="v-list"><div class="secondary--text"><a class="v-list__tile v-list__tile--link"><div class="v-list__tile__avatar"><div class="v-avatar" style="height:40px;width:40px;"><i class="fa fa-lg fa-copy"></i></div></div><div class="v-list__tile__title">复制链接</div></a></div></div><input type="text" tabindex="-1" aria-hidden="true" value="" class="fake-hide"></div></div></div></nav><main class="v-content" style="padding-top:56px;padding-right:0px;padding-bottom:0px;padding-left:0px;"><div class="v-content__wrap"><div class="container blog-container grid-list-xl align-center"><div class="layout row wrap"><div class="flex mb-3 xs12"><article class="v-card elevation-16 post-card" style="height:undefined;"><div class="v-card__title"><div class="flex xs12"><h2 class="display-1 mb-3">28 Scheduler 基本观念</h2><div class="post-meta"><time datetime="2018-04-29T12:16:20.000Z" class="secondary--text post-time">2018年04月29日</time></div></div></div><div class="v-card__text pt-0 pb-0"><div class="flex xs12"><div class="content custom"><h1 id="_30-天精通-rxjs-28-：scheduler-基本观念"><a href="#_30-天精通-rxjs-28-：scheduler-基本观念" aria-hidden="true" class="header-anchor">#</a> 30 天精通 RxJS(28)：Scheduler 基本观念</h1><p>不晓得读者们还记不记得，我们在前面的文章中有提到 Scheduler 是为了解决 RxJS 衍生的最后一个问题，而我们现在就在揭晓这个谜底。</p><p>其实 RxJS 用久了之后就会发现 Observable 有一个优势是可以同时处理同步和非同步行为，但这个优势也带来了一个问题，就是我们常常会搞不清处现在的 observable 执行方式是同步的还是非同步的。换句话说，我们很容易搞不清楚 observable 到底什么时候开始发送元素！</p><p>举例来说，我们可能很清楚 <code>interval</code> 是非同步送出元素的，但 <code>range</code> 呢？ <code>from</code> 呢？他们可能有时候是非同步有时候是同步，这就会变得有点困扰，尤其在除错时执行顺序就非常重要。</p><p>而 Scheduler 基本上就是拿来处理这个问题的！
</p><h2 id="什么是-scheduler？"><a href="#什么是-scheduler？" aria-hidden="true" class="header-anchor">#</a> 什么是 Scheduler？</h2><p>Scheduler 控制一个 observable 的订阅什么时候开始，以及发送元素什么时候送达，主要由以下三个元素所组成</p><ul><li>Scheduler 是一个资料结构。 它知道如何根据优先级或其他标准来储存并伫列任务。</li><li>Scheduler 是一个执行环境。 它意味着任务何时何地被执行，比如像是 立即执行、在回呼(callback)中执行、setTimeout 中执行、animation frame 中执行</li><li>Scheduler 是一个虚拟时钟。 它透过 <code>now()</code> 这个方法提供了时间的概念，我们可以让任务在特定的时间点被执行。</li></ul><p>简言之 Scheduler 会影响 Observable 开始执行及元素送达的时机，比如下面这个例子</p><pre class="language-javascript"><code><span class="token keyword">var</span> observable <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span><span class="token keyword">function</span> <span class="token punctuation">(</span>observer<span class="token punctuation">)</span> <span class="token punctuation">{</span>
    observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    observer<span class="token punctuation">.</span><span class="token function">next</span><span class="token punctuation">(</span><span class="token number">3</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    observer<span class="token punctuation">.</span><span class="token function">complete</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'before subscribe'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
observable<span class="token punctuation">.</span><span class="token function">observeOn</span><span class="token punctuation">(</span>Rx<span class="token punctuation">.</span>Scheduler<span class="token punctuation">.</span><span class="token keyword">async</span><span class="token punctuation">)</span> <span class="token comment">// 设为 async</span>
<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
    next<span class="token punctuation">:</span> <span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    error<span class="token punctuation">:</span> <span class="token punctuation">(</span>err<span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'Error: '</span> <span class="token operator">+</span> err<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">,</span>
    complete<span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'complete'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span><span class="token string">'after subscribe'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// &quot;before subscribe&quot;</span>
<span class="token comment">// &quot;after subscribe&quot;</span>
<span class="token comment">// 1</span>
<span class="token comment">// 2</span>
<span class="token comment">// 3</span>
<span class="token comment">// &quot;complete&quot;</span>

</code></pre><p><a href="https://jsbin.com/sunekab/2/edit?js,console" target="_blank" rel="noopener noreferrer">JSBin</a></p><p>上面这段代码原本是同步执行的，但我们用了 <code>observable.observeOn(Rx.Scheduler.async)</code> 原本是同步执行的就变成了非同步执行了。</p><h2 id="有哪些-scheduler-可以用"><a href="#有哪些-scheduler-可以用" aria-hidden="true" class="header-anchor">#</a> 有哪些 Scheduler 可以用</h2><p>目前 RxJS 5 Scheduler 跟 RxJS 4.x 以前的版本完全不同，在 RxJS 5 当中有提供四个 scheduler，预设为 undefined 会直接以递回的方式执行</p><ul><li>queue 队列</li><li>asap 尽快</li><li>async 异步</li><li>animationFrame 帧动画</li></ul><p>这四个 scheduler 我们会在下面搭配代码一一讲解</p><blockquote><p>RxJS 5 跟 RxJS 4.x 预设的 Scheduler 不同，所以在某些使用情境下会出现不同的结果，例如<a href="https://github.com/ReactiveX/rxjs/issues/1994" target="_blank" rel="noopener noreferrer">这个</a> issue，请特别注意。</p></blockquote><h2 id="使用-scheduler"><a href="#使用-scheduler" aria-hidden="true" class="header-anchor">#</a> 使用 Scheduler</h2><p>其实我们在使用各种不同的 operator 时，这些 operator 就会各自预设不同的 scheduler，例如一个无限的 observable 就会预设为 <code>queue</code> scheduler，而 timer 相关的 operator 则预设为 <code>async</code> scheduler。</p><p>要使用 Scheduler 除了前面用到的 <code>observeOn()</code> 方法外，以下这几个 creation operators 最后一个参数都能接收 Scheduler</p><ul><li>bindCallback</li><li>bindNodeCallback</li><li>combineLatest</li><li>concat</li><li>empty</li><li>from</li><li>fromPromise</li><li>interval</li><li>merge</li><li>of</li><li>range</li><li>throw</li><li>timer</li></ul><p>例如下面这个例子</p><pre class="language-javascript"><code><span class="token keyword">var</span> observable <span class="token operator">=</span> Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token keyword">from</span><span class="token punctuation">(</span><span class="token punctuation">[</span><span class="token number">1</span><span class="token punctuation">,</span><span class="token number">2</span><span class="token punctuation">,</span><span class="token number">3</span><span class="token punctuation">,</span><span class="token number">4</span><span class="token punctuation">,</span><span class="token number">5</span><span class="token punctuation">]</span><span class="token punctuation">,</span> Rx<span class="token punctuation">.</span>Scheduler<span class="token punctuation">.</span><span class="token keyword">async</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre><p>另外还有多个 operators 最后一个参数可以传入 Scheduler 这边就不一一列出，这已参考官方的<a href="http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html" target="_blank" rel="noopener noreferrer">文件</a>，最通用的方式还是 <code>observeOn()</code> 只要是 observable 就可以用这个方法。</p><h3 id="queue"><a href="#queue" aria-hidden="true" class="header-anchor">#</a> queue</h3><p>queue 的运作方式跟预设的立即执行很像，但是当我们使用到递回的方法时，他会伫列这些行为而非直接执行，一个递回的 operator 就是他会执行另一个 operator，最好的例子就是 <code>repeat()</code>，如果我们不给他参数的话，他会执行无限多次，像下面这个例子</p><pre class="language-javascript"><code>Rx<span class="token punctuation">.</span>Observable<span class="token punctuation">.</span><span class="token keyword">of</span><span class="token punctuation">(</span><span class="token number">10</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">repeat</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">take</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span>
<span class="token punctuation">.</span><span class="token function">subscribe</span><span class="token punctuation">(</span>console<span class="token punctuation">.</span>log<span class="token punctuation">)</span><span class="token punctuation">;</span>

</code></pre><p>这个例子在 RxJS 4.x 的版本中执行会使浏览器挂掉，因为 <code>take(1)</code> 永远不会被执行到 <code>repeat</code> 会一直重复要元素，而在 RxJS 5 中他预设了无限的 observable 为 queue 所以他会把 repeat 的 next 行为先伫列起来，因为前一个 complete 还在执行中，而这时 repeat 就会回传一个可退订的物件给 <code>take(1)</code> 等到 repeat 的 next 被第一次执行时就会结束，因为 <code>take(1)</code> 会直接收到值。</p><p><strong>使用情境：</strong></p><p>queue 很适合用在会有递回的 operator 且具有大量资料时使用，在这个情况下 queue 能避免不必要的效能损耗。</p><h3 id="asap"><a href="#asap" aria-hidden="true" class="header-anchor">#</a> asap</h3><p>asap 的行为很好理解，它是非同步的执行，在浏览器其实就是 setTimeout 设为 0 秒 (在 NodeJS 中是用 process.nextTick)，因为行为很好理解这里就不写例子了。</p><p><strong>使用情境：</strong></p><p>asap 因为都是在 setTimeout 中执行，所以不会有 block event loop 的问题，很适合用在永远不会退订的 observable，例如在背景下持续监听 server 送来的通知。</p><h3 id="async"><a href="#async" aria-hidden="true" class="header-anchor">#</a> async</h3><p>这个是在 RxJS 5 中新出现的 Scheduler，它跟 asap 很像但是使用 setInterval 来运作，通常是跟时间相关的 operator 才会用到。</p><h3 id="animationframe"><a href="#animationframe" aria-hidden="true" class="header-anchor">#</a> animationFrame</h3><p>这个相信大家应该都知道，他是利用 <code>Window.requestAnimationFrame</code> 这个 API 去实现的，所以执行周期就跟 <code>Window.requestAnimationFrame</code> 一模一样。</p><p><strong>使用情境：</strong></p><p>在做复杂运算，且高频率触发的 UI 动画时，就很适合使用 animationFrame，以可以搭配 throttle operator 使用。</p><h2 id="今日小结"><a href="#今日小结" aria-hidden="true" class="header-anchor">#</a> 今日小结</h2><p>这篇文章简单的介绍了 RxJS 的 Scheduler，因为篇幅的关系没有办法很细的去讲，但实际上 Scheduler 的使用非常简单，只要在 operator 的最后一个参数加上去或是用 observeOn 就可以了。平常其实不太需要用到 Scheduler，尤其在 RxJS 5 中已经有针对各种情况给不同的预设，笔者最常用到的还是 animationFrame！</p><p>不知道今天读者们有没有收获呢？ 如果有任何疑问，欢迎在下方留言给我，谢谢。</p></div></div></div><div class="v-card__actions"><div class="flex xs12"><a href="/BLOG/tags/RXJS"><span tabindex="0" class="v-chip capitalize chip-tag v-chip--label v-chip--small"><span class="v-chip__content">RXJS</span></span></a></div></div></article></div><div class="flex text-xs-left xs6"><a href="/BLOG/posts/rxjs29.html" class="post-nav v-btn v-btn--flat v-btn--router"><div class="v-btn__content"><div class="grey--text"><i class="fa mr-1 fa-chevron-left"></i>Prev</div><div class="title mt-1 primary--text hidden-xs-only">29 三十天感言</div></div></a></div><div class="flex text-xs-right xs6"><a href="/BLOG/posts/rxjs27.html" class="post-nav v-btn v-btn--flat v-btn--router"><div class="v-btn__content"><div class="grey--text">Next
          <i class="fa ml-1 fa-chevron-right"></i></div><div class="title mt-1 primary--text hidden-xs-only">27 简易实现 Observable(二)</div></div></a></div><div class="flex mt-3 xs12"><div class="v-card" style="height:undefined;"><div class="v-card__title"><span class="headline">Comment</span></div></div></div></div></div><footer class="v-footer blog-footer darken-1 mt-3 theme--dark" style="height:auto;"><div class="primary--text text--lighten-4 text-xs-center py-3 v-card v-card--flat v-card--tile primary" style="height:undefined;"><div class="v-card__text pb-0">博客内容遵循 <a rel="license noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh" target="_blank">知识共享 署名 - 非商业性 - 相同方式共享 4.0 国际协议</a></div><div class="v-card__text pt-0 mt-1"><span>訾绍飞 © 2015 - 2018</span><span><!---->
        Power by
        <a href="https://vuepress.vuejs.org" target="_blank" rel="noopener noreferrer">VuePress</a> Theme
        <a href="https://github.com/ShaofeiZi/BLOG" target="_blank" rel="noopener noreferrer">indigo</a></span></div></div></footer></div></main><button type="button" class="v-btn v-btn--bottom v-btn--floating v-btn--fixed v-btn--right accent" style="display:none;"><div class="v-btn__content"><i class="fa fa-lg fa-chevron-up"></i></div></button></div></div></div>
    <script src="/BLOG/assets/js/6.e51cd79c.js" defer></script><script src="/BLOG/assets/js/app.1a725be8.js" defer></script>
  </body>
</html>
